package com.managertrade.util.strategy;

import com.managertrade.dto.vo.AccountFunds;
import com.managertrade.dto.vo.KlineWithIndicators;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import static com.managertrade.util.strategy.XgbKlinePipeline.DATA_DIR;

public class TrendOscillationStrategyWithSLTP {

    private static final BigDecimal INIT_FUNDS = new BigDecimal("10000");
    private static final BigDecimal FEE_RATE = new BigDecimal("0.001"); // 0.1%
    private static final BigDecimal POSITION_RATIO = BigDecimal.ONE; // 全仓=1
    private static final int WINDOW_SIZE = 720; // 近30天(5m数据)
    private static final BigDecimal ADX_THRESHOLD = new BigDecimal("25"); // 趋势强度阈值
    private static final BigDecimal ATR_MULTIPLIER = new BigDecimal("1"); // 止损/止盈倍数
    private static final BigDecimal SL_PERCENT = new BigDecimal("0.02"); // 止损2%
    private static final BigDecimal TP_PERCENT = new BigDecimal("0.03"); // 止盈3%

    public static void main(String[] args) throws Exception {
        List<KlineWithIndicators> klines = ExcelKlineReader.readExcelToKlines(DATA_DIR + "/BNB-USDT_12h_kline.xlsx");

        BigDecimal cash = INIT_FUNDS;
        BigDecimal position = BigDecimal.ZERO;
        boolean inPosition = false;
        BigDecimal entryPrice = BigDecimal.ZERO;

        List<AccountFunds> history = new ArrayList<>();

        for (int i = 14; i < klines.size(); i++) { // 从第14根开始计算ATR/ADX

            KlineWithIndicators currK = klines.get(i);
            BigDecimal price = BigDecimal.valueOf(currK.getClose());

            // --- 计算 ATR & ADX ---
            BigDecimal atr = calcATR(klines, i, 14);
            BigDecimal adx = calcADX(klines, i, 14);

            // --- 判断行情 ---
            boolean isTrend = adx.compareTo(ADX_THRESHOLD) > 0;
            boolean isOscillation = !isTrend;

            String signal = "HOLD";

            // --- 止损/止盈 ---
            if (inPosition) {
                BigDecimal pnlPercent = price.subtract(entryPrice).divide(entryPrice, 6, RoundingMode.HALF_UP);
                if (pnlPercent.compareTo(SL_PERCENT.negate()) <= 0) {
                    // 止损
                    cash = cash.add(position.multiply(price).multiply(BigDecimal.ONE.subtract(FEE_RATE)));
                    position = BigDecimal.ZERO;
                    inPosition = false;
                    signal = "STOP LOSS";
                } else if (pnlPercent.compareTo(TP_PERCENT) >= 0) {
                    // 止盈
                    cash = cash.add(position.multiply(price).multiply(BigDecimal.ONE.subtract(FEE_RATE)));
                    position = BigDecimal.ZERO;
                    inPosition = false;
                    signal = "TAKE PROFIT";
                }
            }

            // --- 策略信号 ---
            if (!inPosition) {
                if (isTrend) {
                    // 趋势行情 → 阈值突破
                    BigDecimal delta = price.subtract(BigDecimal.valueOf(klines.get(i - 1).getClose()));
                    BigDecimal deltaPct = delta.divide(BigDecimal.valueOf(klines.get(i - 1).getClose()), 6, RoundingMode.HALF_UP);
                    if (deltaPct.compareTo(BigDecimal.ZERO) > 0) {
                        // 顺势开多
                        position = cash.multiply(POSITION_RATIO).divide(price, 6, RoundingMode.HALF_UP);
                        cash = cash.subtract(position.multiply(price)).subtract(position.multiply(price).multiply(FEE_RATE));
                        entryPrice = price;
                        inPosition = true;
                        signal = "TREND BUY";
                    } else if (deltaPct.compareTo(BigDecimal.ZERO) < 0) {
                        // 顺势开空（可扩展空仓逻辑）
                        // position = cash.multiply(POSITION_RATIO).divide(price, 6, RoundingMode.HALF_UP);
                        // inPosition = true;
                        // entryPrice = price;
                        // signal = "TREND SELL";
                    }
                } else {
                    // 震荡行情 → 区间反转
                    BigDecimal recentHigh = getRecentHigh(klines, i, WINDOW_SIZE);
                    BigDecimal recentLow = getRecentLow(klines, i, WINDOW_SIZE);

                    if (price.compareTo(recentLow) <= 0) {
                        // 低位买入
                        position = cash.multiply(POSITION_RATIO).divide(price, 6, RoundingMode.HALF_UP);
                        cash = cash.subtract(position.multiply(price)).subtract(position.multiply(price).multiply(FEE_RATE));
                        entryPrice = price;
                        inPosition = true;
                        signal = "OSC BUY";
                    } else if (price.compareTo(recentHigh) >= 0) {
                        // 高位卖出
                        position = cash.multiply(POSITION_RATIO).divide(price, 6, RoundingMode.HALF_UP);
                        cash = cash.subtract(position.multiply(price)).subtract(position.multiply(price).multiply(FEE_RATE));
                        entryPrice = price;
                        inPosition = true;
                        signal = "OSC SELL";
                    }
                }
            }

            BigDecimal totalFunds = cash.add(position.multiply(price));
            AccountFunds f = new AccountFunds();
            f.setTimestamp(LocalDateTime.ofInstant(java.time.Instant.ofEpochMilli(currK.getTimestamp()), java.time.ZoneId.systemDefault()));
            f.setCashBalance(cash);
            f.setPositionValue(position.multiply(price));
            f.setProfitLoss(totalFunds.subtract(INIT_FUNDS));
            history.add(f);

            System.out.println(f.getTimestamp() +
                    " | price: " + price.setScale(4, RoundingMode.HALF_UP) +
                    " | ADX: " + adx.setScale(2, RoundingMode.HALF_UP) +
                    " | ATR: " + atr.setScale(4, RoundingMode.HALF_UP) +
                    " | Signal: " + signal +
                    " | Cash: " + cash.setScale(2, RoundingMode.HALF_UP) +
                    " | Position: " + position.setScale(4, RoundingMode.HALF_UP));
        }

        BigDecimal finalFunds = cash.add(position.multiply(BigDecimal.valueOf(klines.get(klines.size() - 1).getClose())));
        System.out.println("\n=== 回测结果汇总 ===");
        System.out.println("初始资金: " + INIT_FUNDS);
        System.out.println("最终资金: " + finalFunds.setScale(2, RoundingMode.HALF_UP));
        System.out.println("收益率: " + finalFunds.subtract(INIT_FUNDS).divide(INIT_FUNDS, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP) + "%");
    }

    private static BigDecimal calcATR(List<KlineWithIndicators> klines, int idx, int period) {
        int start = Math.max(1, idx - period + 1);
        BigDecimal sumTR = BigDecimal.ZERO;
        for (int i = start; i <= idx; i++) {
            BigDecimal high = BigDecimal.valueOf(klines.get(i).getHigh());
            BigDecimal low = BigDecimal.valueOf(klines.get(i).getLow());
            BigDecimal prevClose = BigDecimal.valueOf(klines.get(i - 1).getClose());
            BigDecimal tr = high.subtract(low).max(high.subtract(prevClose)).max(prevClose.subtract(low));
            sumTR = sumTR.add(tr);
        }
        return sumTR.divide(BigDecimal.valueOf(idx - start + 1), 6, RoundingMode.HALF_UP);
    }

    private static BigDecimal calcADX(List<KlineWithIndicators> klines, int idx, int period) {
        int start = Math.max(1, idx - period + 1);
        BigDecimal sum = BigDecimal.ZERO;
        for (int i = start; i <= idx; i++) {
            BigDecimal high = BigDecimal.valueOf(klines.get(i).getHigh());
            BigDecimal low = BigDecimal.valueOf(klines.get(i).getLow());
            BigDecimal prevHigh = BigDecimal.valueOf(klines.get(i - 1).getHigh());
            BigDecimal prevLow = BigDecimal.valueOf(klines.get(i - 1).getLow());
            BigDecimal upMove = high.subtract(prevHigh);
            BigDecimal downMove = prevLow.subtract(low);
            BigDecimal dm = upMove.subtract(downMove).abs();
            sum = sum.add(dm);
        }
        return sum.divide(BigDecimal.valueOf(idx - start + 1), 6, RoundingMode.HALF_UP);
    }

    private static BigDecimal getRecentHigh(List<KlineWithIndicators> klines, int idx, int window) {
        int start = Math.max(0, idx - window + 1);
        BigDecimal high = BigDecimal.valueOf(klines.get(start).getHigh());
        for (int i = start + 1; i <= idx; i++) {
            BigDecimal h = BigDecimal.valueOf(klines.get(i).getHigh());
            if (h.compareTo(high) > 0) high = h;
        }
        return high;
    }

    private static BigDecimal getRecentLow(List<KlineWithIndicators> klines, int idx, int window) {
        int start = Math.max(0, idx - window + 1);
        BigDecimal low = BigDecimal.valueOf(klines.get(start).getLow());
        for (int i = start + 1; i <= idx; i++) {
            BigDecimal l = BigDecimal.valueOf(klines.get(i).getLow());
            if (l.compareTo(low) < 0) low = l;
        }
        return low;
    }
}
